/*
XBaseTools
Copyright (C) 2008 Matthew Perry

This library/program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This libary/program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Collections.Generic;
using System.IO;
using BaseTools.Strings;
using System.Text;
using BaseTools.Configuration;
using BaseTools.Helpers;

namespace BaseTools.Configuration.Parsers
{
	/// <summary>
	/// Nested reader/writer that works with Doom Builder configurations
	/// </summary>
	public class BuilderConfig : IConfig
	{
		private static StringParser SourceParser = new StringParser(BuilderConfig.BuilderParseInfo());
		
		/// <summary>
		/// Creates a new BuilderConfig
		/// </summary>
		public BuilderConfig()
		{
		}
		
		/// <summary>
		/// The ParseInfo used by Source configurations
		/// </summary>
		/// <returns>Source ParseInfo</returns>
		public static ParseInfo BuilderParseInfo()
		{
			return new ParseInfo('\"', '\\', new char[] { ';', '=', '{', '}'}, new EscapeSequence[] { new EscapeSequence('\\', '\\'), new EscapeSequence('n', '\n'),
			                     	new EscapeSequence('r', '\r'), new EscapeSequence('t', '\t'), new EscapeSequence('\"', '\"') },
			                     new char[] {' ', '\r', '\n', '\t'}, new char[] {'\r', '\n'});
			//new char[] {'?', 'n', 'r', 't', '\"'}, new char[] {'?', '\n', '\r', '\t', '\"'}
		}
		
		/// <summary>
		/// Saves the configuration to a file
		/// </summary>
		/// <param name="filename">The file to save to</param>
		/// <param name="lst">The ConfigList to save</param>
		public void SaveConfiguration(string filename, ConfigList lst)
		{
			File.WriteAllText(IOHelper.CleanPath(filename), WriteConfiguration(lst));
		}
		
		/// <summary>
		/// Creates a string representing the configuration
		/// </summary>
		/// <returns>The string respresentation of the configuration</returns>
		public string WriteConfiguration(ConfigList lst)
		{
			return WriteConfiguration(lst, 0);
		}
		private string WriteConfiguration(ConfigList config, int scope)
		{
			StringBuilder outputData = new StringBuilder();
			WriteConfiguration(config, 0, outputData);
			
			return outputData.ToString();
		}
		private void WriteConfiguration(ConfigList config, int scope, StringBuilder outputData)
		{
			// Obviously there is an endless loop by now
			if (scope > 32767)
				return;
			
			string tabs = "";
			for(int i=0;i<scope;i++)
				tabs += "\t";
			
			List<ConfigPair> pairs = config.GetList();
			
			foreach(ConfigPair pair in pairs)
			{
				if (pair.Key == null)
					continue;
				
				if (pair.Data == null)
				{
					outputData.Append(tabs);
					autokey(pair, outputData);
					
					outputData.Append(";\r\n");
				}
				else if (pair.DataIsString)
				{
					outputData.Append(tabs);
					autokey(pair, outputData);
					
					outputData.Append(" = \"");
					SourceParser.EncodeString(pair.DataAsString, outputData);
					outputData.Append("\";\r\n");
				}
				else if (pair.DataIsInteger)
				{
					outputData.Append(tabs);
					autokey(pair, outputData);
					outputData.Append(" = ").Append((int)pair.Data).Append(";\r\n");
				}
				else if (pair.DataIsDouble)
				{
					outputData.Append(tabs);
					autokey(pair, outputData);
					
					double dbl = pair.DataAsDouble;
					
					outputData.Append(" = ");
					if ((dbl % 1.00) != 0.00)
						outputData.Append((double)pair.Data).Append(";\r\n");
					else
						outputData.Append((double)pair.Data).Append(".00;\r\n");
				}
				else if (pair.DataIsList)
				{
					if (pair.DataAsList != config)
					{
						outputData.Append(tabs);
						autokey(pair, outputData);
						outputData.Append("\r\n");
						
						outputData.Append(tabs).Append("{\r\n");
						WriteConfiguration(pair.DataAsList, scope + 1, outputData);
						outputData.Append(tabs).Append("}\r\n");
					}
				}
			}
		}
		
		private void autokey(ConfigPair pair, StringBuilder sb)
		{
			if (/*pair.KeyQuoted || */ShouldQuote(pair.Key))
			{
				sb.Append('"');
				SourceParser.EncodeString(pair.Key, sb);
				sb.Append('"');
			}
			else
				sb.Append(pair.Key);
		}
		private bool ShouldQuote(string data)
		{
			return data.Contains(" ") || data.Contains("\t") || data.Contains("\r") || data.Contains("\n") || data.Contains("=") || data.Contains(";") || data.Contains("\\") || data.Contains("\"");;
		}
		
		/// <summary>
		/// Imports a configuration from file
		/// </summary>
		/// <param name="filename">The file to read the configuration from</param>
		public ConfigList LoadConfiguration(string filename)
		{
			return ReadConfiguration(File.ReadAllText(IOHelper.CleanPath(filename)));
		}
		
		/// <summary>
		/// Imports a configuration
		/// </summary>
		/// <param name="data">The file data</param>
		public ConfigList ReadConfiguration(string data)
		{
			return ReadConfiguration(new StringCharStream(data));
		}
		
		private ConfigList ReadConfiguration(ICharStream stream)
		{
			int prt = 0;
			bool kq = false;
			string key = null;
			ConfigList list = new ConfigList();
			
			while(stream.Available())
			{
				StringBlock block = SourceParser.ReadBlock(stream);
				
				if (!block.Quoted && block.Data == "//")
				{
					SourceParser.SkipLine(stream);
				}
				else if (!block.Quoted && block.Data == "{" && prt == 1)
				{
					list.AddPair(new ConfigPair(key, ReadConfiguration(stream), kq, false));
					prt = 0;
				}
				else if (!block.Quoted && block.Data == "}" && prt == 0)
				{
					return list;
				}
				else if (!block.Quoted && (prt == 2))
				{
					if (block.Data.Contains("."))
					{
						double dbl = 0;
						if (double.TryParse(block.Data, out dbl))
						{
							list.AddPair(new ConfigPair(key, dbl, kq, false));
						}
						else
						{
							list.AddPair(new ConfigPair(key, null, kq, false));
						}
					}
					else
					{
						int i = 0;
						if (int.TryParse(block.Data, out i))
						{
							list.AddPair(new ConfigPair(key, i, kq, false));
						}
						else
						{
							list.AddPair(new ConfigPair(key, null, kq, false));
						}
					}
					
					prt = 3;
				}
				else if (prt == 1 && block.Data == "=" && !block.Quoted)
				{
					prt = 2;
				}
				else if (prt == 3 && block.Data == ";" && !block.Quoted)
				{
					prt = 0;
				}
				else if (prt == 1 && block.Data == ";" && !block.Quoted)
				{
					list.AddPair(new ConfigPair(key, null, kq, false));
					prt = 0;
				}
				else
				{
					if (prt == 0)
					{
						key = block.Data;
						kq = block.Quoted;
						prt = 1;
					}
					else if (prt == 2)
					{
						list.AddPair(new ConfigPair(key, block.Data, kq, block.Quoted));
						prt = 3;
					}
				}
			}
			
			return list;
		}
	}
}
